//Crazy Intro Loader, Copyright 2009 WiiCrazy/I.R.on of Irduco (nejat@tepetaklak.com)
//Distributed under the terms of the GNU GPL v2.0
//See http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt for more information

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <fat.h>
#include <string.h>
#include <gccore.h>
#include <sys/dir.h>
#include <unistd.h>
#include <errno.h>
#include "elf.h"
#include "dol.h"

#include <ogc/machine/asm.h>
#include <ogc/machine/processor.h>

#define DOL_TO_LAUNCH	0x80002F00
#define EXECUTABLE_MEM_ADDR 0x92000000

u8 HWButton = 1;
static void *xfb = NULL;
static GXRModeObj *rmode = NULL;

void console_setup(void) {
  VIDEO_Init();
  
  switch(VIDEO_GetCurrentTvMode()) {
  case VI_NTSC:
    rmode = &TVNtsc480IntDf;
    break;
  case VI_PAL:
    rmode = &TVPal528IntDf;
    break;
  case VI_MPAL:
    rmode = &TVMpal480IntDf;
    break;
  default:
    rmode = &TVNtsc480IntDf;
    break;
  }
  
  xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
  console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ);
  
  VIDEO_Configure(rmode);
  VIDEO_SetNextFramebuffer(xfb);
  VIDEO_SetBlack(FALSE);
  VIDEO_Flush();
  VIDEO_WaitVSync();
  if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
}

//We won't give access to power&reset though...
/**
 * Callback for the reset button on the Wii.
 */
void WiiResetPressed()
{
	HWButton = SYS_RETURNTOMENU;
}
 
/**
 * Callback for the power button on the Wii.
 */
void WiiPowerPressed()
{
	HWButton = SYS_POWEROFF_STANDBY;
}
 
void exitToSystemMenu() 
{
	SYS_ResetSystem(SYS_RETURNTOMENU, 0, 0);	
}


void my_exit()
{
	SYS_ResetSystem(HWButton, 0, 0);
	exit(0);
}

void initialise() {
	SYS_SetResetCallback(WiiResetPressed);
	SYS_SetPowerCallback(WiiPowerPressed);
	console_setup();
	printf("\n\nCrazy Intro Dol/Elf Loader\n");		
	printf("\n\nLoader Initialized\n");	
}


typedef void (*entrypoint) (void);


void fatal(char * err) 
{
	printf(err);
	printf("\n\nExiting to System Menu");
	sleep(5);
	exitToSystemMenu();
}


void * loadExecutableIntoMem(char * exeFilePath) 
{
    u32 cookie;
    
    int ret;
    
	printf("\nBooting dol/elf:  %s", exeFilePath);
	
    FILE *exeFile;
    void *exeBuffer          = (void *)EXECUTABLE_MEM_ADDR;
    int exeSize              = 0;
    int isExeValid           = 0;	
    u32 exeEntryPointAddress = 0;
    entrypoint exeEntryPoint;

	
	exeEntryPointAddress = 0;

	printf("\nInitialize fat");
	
	bool result = fatInitDefault();
	
	if (!result) 
	{
		fatal("Can't mount fat!");
	}

	printf("\nOpening file");
	
    exeFile = fopen (exeFilePath ,"rb");

    if (exeFile==NULL) 
	{
		fatal("Cannot open file!");
    }
	

    fseek (exeFile, 0, SEEK_END);
    exeSize = ftell(exeFile);
	
	printf("\nSize : %d", exeSize);
	
    fseek (exeFile, 0, SEEK_SET);
       
 	printf("\nSeeked to beginning, reading into buffer");
	
	exeBuffer = malloc(exeSize);
	
	if (!exeBuffer) 
	{
		fatal("Can't allocate memory for the dol!");	
	}
	
    if(fread (exeBuffer, 1, exeSize, exeFile) != exeSize) {	
		fatal("Error Reading");
    }
	
	DCFlushRangeNoSync ((void*)exeBuffer, exeSize);

	printf("\nUnmounting fat");
    fatUnmount("sd");   
    sdio_Deinitialize();
	
	return exeBuffer;
}



void loadAndLaunchExecutable(char * exeFilePath) 
{
    u32 cookie;   
    int ret;

    //FILE *exeFile;
    //void *exeBuffer          = (void *)EXECUTABLE_MEM_ADDR;
	void *exeBuffer = 	loadExecutableIntoMem(exeFilePath);
    int isExeValid           = 0;	
    u32 exeEntryPointAddress = 0;
    entrypoint exeEntryPoint;
	
	printf("\nLoading executable from memory");	
	
    isExeValid = valid_elf_image(exeBuffer);
	
    if (isExeValid < 0) {
        fatal("Not a valid elf file!");
    }

	struct __argv args[10];

    if (isExeValid == 1) {
        exeEntryPointAddress = load_elf_image(exeBuffer);
    } else {
        exeEntryPointAddress = load_dol_image(exeBuffer, args);
    }


	printf("\nFreeing allocated memory");
	free(exeBuffer);
	
	printf("\nLoaded into memory, entry point is %x\n", exeEntryPointAddress);	
    
	printf("\nLaunching executable\n");	
    if (exeEntryPointAddress == 0) {
        fatal("Invalid entry point!");
    }
	
	sleep(5);

    exeEntryPoint = (entrypoint) exeEntryPointAddress;
  
    __IOS_ShutdownSubsystems ();
    _CPU_ISR_Disable (cookie);
    __exception_closeall ();
    exeEntryPoint ();
    _CPU_ISR_Restore (cookie);
}


//---------------------------------------------------------------------------------
int main(int argc, char **argv) {
//--------------------------------------------------------------------------------
	initialise();
	
    char dolFile[256];
	strcpy(dolFile, (char *) (DOL_TO_LAUNCH));
	printf("\nDol to launch is : %s", dolFile);
	loadAndLaunchExecutable(dolFile);
}
